package com.neusoft.hifly.security;

import java.io.IOException;
import java.text.NumberFormat;
import java.util.Random;

/**
 * 等长数字认证码的加密解密源程序
 * <p>
 * ★ 认证编码由10位数字组成
 */
public class Digit10Engine {
	public static final int ENCODE_TIMES = 16; // times >6 后 每位数字至少被运算2次

	static Random random = new Random();

	static int swapBox10[][] = { { 2, 3, 7, 5, 4, 9, 8, 6, 1, 0 }, { 3, 7, 4, 6, 9, 5, 1, 0, 2, 8 },
			{ 0, 5, 2, 1, 9, 8, 3, 6, 7, 4 }, { 5, 9, 4, 6, 7, 3, 2, 8, 0, 1 }, { 9, 5, 3, 1, 4, 6, 0, 7, 2, 8 },
			{ 2, 6, 4, 8, 0, 7, 3, 5, 1, 9 }, { 4, 2, 7, 6, 0, 3, 1, 5, 9, 8 }, { 7, 4, 0, 2, 6, 8, 9, 5, 1, 3 } };

	static int swapBox32[][] = {
			{ 5, 27, 26, 4, 2, 31, 17, 12, 1, 22, 29, 24, 11, 16, 6, 0, 8, 7, 10, 21, 19, 3, 14, 28, 23, 13, 9, 30, 18,
					20, 15, 25 },
			{ 6, 14, 26, 15, 1, 12, 4, 9, 19, 16, 27, 3, 23, 31, 18, 11, 2, 28, 8, 0, 20, 30, 13, 17, 10, 21, 5, 29, 22,
					25, 24, 7 },
			{ 17, 23, 27, 22, 13, 10, 14, 19, 25, 21, 18, 20, 28, 6, 15, 29, 3, 16, 31, 12, 0, 26, 2, 9, 11, 7, 8, 1, 4,
					30, 24, 5 },
			{ 23, 13, 20, 26, 8, 24, 5, 29, 19, 10, 2, 1, 30, 27, 12, 3, 15, 25, 31, 17, 6, 0, 11, 14, 16, 21, 28, 18,
					22, 4, 7, 9 },
			{ 5, 3, 26, 12, 8, 4, 16, 21, 29, 27, 23, 2, 24, 10, 1, 0, 17, 31, 15, 25, 13, 14, 30, 7, 9, 11, 19, 22, 6,
					20, 18, 28 },
			{ 24, 5, 20, 21, 0, 14, 23, 31, 28, 2, 22, 10, 26, 4, 16, 1, 27, 18, 3, 13, 9, 6, 19, 17, 25, 8, 15, 11, 29,
					12, 7, 30 },
			{ 3, 28, 7, 12, 10, 31, 17, 4, 15, 14, 0, 18, 13, 16, 21, 26, 11, 9, 19, 5, 24, 29, 1, 8, 30, 27, 25, 22,
					23, 2, 20, 6 },
			{ 25, 27, 20, 7, 3, 9, 21, 10, 1, 13, 29, 15, 5, 16, 0, 22, 6, 17, 31, 2, 28, 23, 24, 26, 30, 14, 4, 19, 18,
					11, 12, 8 } };

	/**
	 * 32位字符替换盒
	 *
	 * @param origStr
	 *            要做位替换的原始字串
	 * @param boxID
	 *            选用的盒子编号
	 * @return
	 */
	static String swapChar32(final String origStr, int boxID) {
		String result = new String();
		boxID = boxID % 8;
		for (int i = 0; i < 32; i++) {
			result = result + origStr.charAt(Digit10Engine.swapBox32[boxID][i]);
		}
		return result;
	}

	static String unSwapChar32(final String origStr, int boxID) {
		String result = new String();
		boxID = boxID % 8;
		final char temp[] = origStr.toCharArray();
		for (int i = 0; i < 32; i++) {
			temp[Digit10Engine.swapBox32[boxID][i]] = origStr.charAt(i);
		}
		for (int i = 0; i < 32; i++) {
			result = result + temp[i];
		}
		return result;
	}

	/**
	 * 10位字符的替换盒
	 *
	 * @param origStr
	 *            要做位替换的原始字串
	 * @param boxID
	 *            选用的盒子编号
	 * @return
	 * @throws Digit10Exception
	 */
	static String swapChar10(final String origStr, int boxID) {
		String result = new String();
		boxID = boxID % 8;

		for (int i = 0; i < 10; i++) {
			result = result + origStr.charAt(Digit10Engine.swapBox10[boxID][i]);
		}
		return result;
	}

	static String unSwapChar10(final String origStr, int boxID) {
		String result = new String();
		boxID = boxID % 8;
		final char temp[] = origStr.toCharArray();
		for (int i = 0; i < 10; i++) {
			temp[Digit10Engine.swapBox10[boxID][i]] = origStr.charAt(i);
		}
		for (int i = 0; i < 10; i++) {
			result = result + temp[i];
		}
		return result;
	}

	/**
	 * 对key进行keyMask操作,并返回最终key的二进制字符串
	 *
	 * @param key
	 *            编码CouponBuyID 时用的session key
	 * @param keyMask
	 *            使外界对相同的key可以分成不通的领域进行独立控制
	 * @return 处理过的key的二进制字符串
	 * @throws Digit10Exception
	 */
	static String getKeyBinString(final String key, final String keyMask) throws IOException {

		// 对key进行预处理,让key保持10位长度的字符串
		final int k = key.hashCode();
		final int km = keyMask.hashCode();
		long x = k ^ km;

		final NumberFormat form = NumberFormat.getInstance();
		form.setGroupingUsed(false);
		form.setMinimumFractionDigits(0);
		form.setMaximumFractionDigits(0);
		form.setMinimumIntegerDigits(10);

		final String k1 = form.format(k);
		final String k2 = form.format(km);
		final String tempkey = ShaUtil.toSha1String(k1 + k2);

		// 将k转换为二进制字符串
		for (int j = 0; j < 39; j++) {
			if (j % 2 == 0) {
				x = Math.abs((((x ^ tempkey.charAt(j)) << 2) ^ km) % 1000000000);
			} else {
				x = x ^ tempkey.charAt(j);
			}
		}

		// 将输入key二进制形式
		final String r = Long.toBinaryString(x);
		if (r.length() > 56) {
			r.substring(0, 55);
		}
		return r;
	}

	/**
	 * 对输入的字符串根据给定的方向进行左或右的循环移位操作,移动步长>0
	 *
	 * @param s
	 *            原始字符串
	 * @param mode
	 *            移位方向
	 *            <li>SHIFT_RIGHT = 1;
	 *            <li>SHIFT_LEFT = -1
	 * @param step
	 *            移位的次数
	 * @return
	 */
	final static int SHIFT_RIGHT = 1;

	final static int SHIFT_LEFT = -1;

	static String strCycleShift(final String s, final int mode, int step) {
		String result = s;

		// 移位次数控制在字符串长度以内
		step = step % s.length();
		if (step <= 0) {
			return s;
		}

		// 根据方向进行移位
		switch (mode) {
			case SHIFT_LEFT:
				step = step + 1;
				result = result.substring(step - 1, result.length()) + result.substring(0, step - 1);
				break;
			case SHIFT_RIGHT:
				result = result.substring(result.length() - step, result.length())
						+ result.substring(0, result.length() - step);
				break;
		}

		return result;

	}

	/**
	 * 编码原则 ★ 加密算法用到的数学性质. 10位数字的异或且不比原先数字的位数多的方法. n位的十进制位数可以最多 (2的n次方-1) 进行异或,其结果仍保持n位十进制数 否则将有可能扩大原来的十进制数字的位数.
	 *
	 * @param origNumber
	 *            CouponBuy_ID原始数值10位数字长
	 * @param key
	 *            加密用的十进制数字6位,范围是[100000,999999]
	 * @param keyMask
	 *            key异或mask,使其避免出现过多的连续0或1,mask可任意确定
	 * @param times
	 *            循环编码的轮次数
	 * @return 编码后的Coupon Buy ID 编码 10位数字
	 * @throws Digit10Exception
	 * @throws IOException
	 * @throws IOException
	 */
	public static String encode(final String origNumber, final String key, final String keyMask, final int times) {
		String result = origNumber;
		int splitPos = origNumber.length() / 2;
		int origLen = origNumber.length();

		// 如果origNumber的长度为奇数则在字符串前补一个空格
		if (origLen % 2 == 1) {
			origLen = origLen + 1;
			splitPos = origLen / 2;
			result = "0" + origNumber;
		}

		if (times < 6) {
			throw new IllegalArgumentException("[times] to be encode is too small, [times] must > 6 .");
		}

		String k = null;
		try {
			k = Digit10Engine.getKeyBinString(key, keyMask);
		} catch (final IOException e) {
			e.printStackTrace();
			return "";
		}

		final NumberFormat form = NumberFormat.getInstance();
		form.setGroupingUsed(false);
		form.setMinimumFractionDigits(0);
		form.setMaximumFractionDigits(0);
		form.setMinimumIntegerDigits(splitPos);

		for (int i = 1; i <= times; i++) {
			result = Digit10Engine.swapChar32(result, i);
			final long r1 = Long.parseLong(result.substring(0, splitPos));
			final long r2 = Long.parseLong(result.substring(splitPos, origLen));
			final long k1 = Long.valueOf(k.substring(0, splitPos), 2).intValue();
			final long k2 = Long.valueOf(k.substring(k.length() - splitPos, k.length()), 2).intValue();
			final String m1 = form.format(r1 ^ k1);
			final String m2 = form.format(r2 ^ k2);
			result = m1 + m2;
			k = Digit10Engine.strCycleShift(k, Digit10Engine.SHIFT_RIGHT, 1);
		}
		return result;
	}

	/**
	 * @param codeNumber
	 *            CouponBuy_ID编码后的值10位数字长
	 * @param key
	 *            加密用的十进制数字6位,范围是[100000,999999]
	 * @param keyMask
	 *            key异或mask,使其避免出现过多的连续0或1,mask可任意确定
	 * @param times
	 *            循环编码的轮次数
	 * @return 解码后的Coupon Buy ID 编码 10位数字
	 * @throws Digit10Exception
	 */
	public static String decode(final String codeNumber, final String key, final String keyMask, int times)
			throws IOException {
		String code = codeNumber;
		final int splitPos = codeNumber.length() / 2;
		final int origLen = codeNumber.length();
		String k = Digit10Engine.getKeyBinString(key, keyMask);
		times = times - 1;
		k = Digit10Engine.strCycleShift(k, Digit10Engine.SHIFT_RIGHT, times);
		final NumberFormat form = NumberFormat.getInstance();
		form.setGroupingUsed(false);
		form.setMinimumFractionDigits(0);
		form.setMaximumFractionDigits(0);
		form.setMinimumIntegerDigits(splitPos);
		for (int i = times; i >= 0; i--) {
			final long m1 = Long.parseLong(code.substring(0, splitPos));
			final long m2 = Long.parseLong(code.substring(splitPos, origLen));
			final long k1 = Long.valueOf(k.substring(0, splitPos), 2).intValue();
			final long k2 = Long.valueOf(k.substring(k.length() - splitPos, k.length()), 2).intValue();
			final String r1 = form.format(m1 ^ k1);
			final String r2 = form.format(m2 ^ k2);
			code = r1 + r2;
			k = Digit10Engine.strCycleShift(k, Digit10Engine.SHIFT_LEFT, 1);
			code = Digit10Engine.unSwapChar32(code, i + 1);
		}
		return code;
	}

	/**
	 * 10位数字的异或且不比原先数字的位数多的方法. , n位的十进制位数 可以最多2 n次方进行异或,其结果保持n位十进制数否则将扩大原来的十进制数字的位数.
	 *
	 * @param args
	 * @throws Digit10Exception
	 */
	public static void main(final String[] args) throws IOException {

		final String s = "12342321331234232133123456789098";
		final String key = "ewqfdfdsasafsfsa4";
		final String keyMask = "22332dsfdafsad432432423222";
		final int times = 36;// 36;

		final String encodeCID = Digit10Engine.encode(s, key, keyMask, Digit10Engine.ENCODE_TIMES);
		final String decodeCID = Digit10Engine.decode(encodeCID, key, keyMask, Digit10Engine.ENCODE_TIMES);

		System.out.println("coupon buy ID :" + s + "  key:" + key + "  keyMask:" + keyMask + "  times:" + times);
		System.out.println("encode ID     :" + encodeCID);
		System.out.println("decode ID     :" + decodeCID);
	}
}
